﻿/*
 * Copyright (C) 2008 Jeremiah Johnson
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using ControlThink.ZWave;
using System.IO;
using Microsoft.Win32;

namespace HouseOfTheFuture
{
    class Program
    {
        #region Variables
        /// <summary>
        /// Timer to check the current light levels and pending ejects
        /// </summary>
        static System.Threading.Timer timer;
        /// <summary>
        /// Used to allow only one thread to update the database at a time
        /// </summary>
        static object IsUpdating;
        ///// <summary>
        ///// ZWave controller
        ///// </summary>
        //static ZWaveController controller;
        /// <summary>
        /// Time the database was last updated
        /// </summary>
        static DateTime dbUpdated;
        /// <summary>
        /// Full path to the database file
        /// </summary>
        static string database;
        /// <summary>
        /// HID path for the Disk Stakka
        /// </summary>
        static string path;
        /// <summary>
        /// Used to allow only one thread to process pending ejects
        /// </summary>
        static object ejecting;
        /// <summary>
        /// # of communications with the Disk Stakka
        /// </summary>
        static int id = 0;
        /// <summary>
        /// How much information will be logged, 2 = Error Only, 1 = Errors & Warnings, 0 = All debug info
        /// </summary>
        static int logLevel = 0;
        /// <summary>
        /// Connection to the Carousel plugin
        /// </summary>
        static Carousel carousel;
        /// <summary>
        /// Lighting plugin
        /// </summary>
        static Lighting lighting;
        /// <summary>
        /// State object for the timer 
        /// </summary>
        class state
        {
            public System.Threading.Timer timer;
        }
        #endregion

        /// <summary>
        /// Main Program Loop
        /// </summary>
        /// <param name="args">Not processed, will always return null</param>
        static void Main(string[] args)
        {
            //Read the registry to get config info

            RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\House of the Future", true);
            if (key == null)
            {
                Console.WriteLine("Creating Registry keys");
                key = Registry.LocalMachine.CreateSubKey("Software\\House of the Future");
                key = Registry.LocalMachine.OpenSubKey("Software\\House of the Future", true);
                key.SetValue("database", AppDomain.CurrentDomain.BaseDirectory + "\\jHome.jdb");
                System.Net.IPAddress[] ips = System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName());
                key.SetValue("irCommander", ips[0].ToString());
                key.SetValue("logLevel", "2");
                key.SetValue("foreColor", System.Drawing.Color.White.ToArgb().ToString());
                key.SetValue("backColor", System.Drawing.Color.SteelBlue.ToArgb().ToString());
                key.SetValue("carousel", "HouseOfTheFuture.DiskStakkaManager");
                key.SetValue("lighting", "HouseOfTheFuture.ControlThinkLighting");
                ips = null;
            }
            if (logLevel < 2)
            {
                Console.WriteLine("Reading Registry keys");
            }
            database = key.GetValue("database").ToString();
            logLevel = int.Parse(key.GetValue("logLevel").ToString());
            
            if (key.GetValue("carousel") == null)
            {
                key.SetValue("carousel", "HouseOfTheFuture.DiskStakkaManager");
            }
            if (key.GetValue("lighting") == null)
            {
                key.SetValue("lighting", "HouseOfTheFuture.ControlThinkLighting");
            }

            //Load Plugins
            if (logLevel < 2)
            {
                Console.WriteLine("Loading Plugins");
            }
            List<PluginManager.Plugin> plugins;
            plugins = PluginManager.LoadPlugins(AppDomain.CurrentDomain.BaseDirectory, "Carousel");

            //Find the Carousel and start the listener
            if (plugins.Count > 0)
            {
                bool bFound = false;
                for (int i = 0; i < plugins.Count; i++)
                {
                    if (plugins[i].TypeName == key.GetValue("carousel").ToString())
                    {
                        carousel = (Carousel)PluginManager.CreateInstance(plugins[i]);
                        FindCarousel();
                        bFound = true;
                        break;
                    }
                }
                if (!bFound)
                {
                    Console.WriteLine("Carousel Plugin not found.  Make sure the plugin is in the directory and use the \"Load Carousel\" command to reload.");
                }
            }
            plugins = PluginManager.LoadPlugins(AppDomain.CurrentDomain.BaseDirectory, "Lighting");

            //Find the Lighting controller
            if (plugins.Count > 0)
            {
                bool bFound = false;
                for (int i = 0; i < plugins.Count; i++)
                {
                    if (plugins[i].TypeName == key.GetValue("lighting").ToString())
                    {
                        lighting = (Lighting)PluginManager.CreateInstance(plugins[i]);
                        lighting.database = database;
                        bFound = true;
                        break;
                    }
                }
                if (!bFound)
                {
                    Console.WriteLine("Lighting Plugin not found.  Make sure the plugin is in the directory and use the \"Load Lighting\" command to reload.");
                }
            }
            plugins = null;
            key = null;
            
            ejecting = true;
            
            IsUpdating = new object();
            //Set the database last update time = now
            dbUpdated = DateTime.Now;
            
            //Create and connect the Lighting controller
            lighting.Initialize();
            //Set up the Timer
            state state = new state();
            System.Threading.TimerCallback timerDelegate = new System.Threading.TimerCallback(timer_tick);

            timer = new System.Threading.Timer(timerDelegate, state, 0, 100);
            state.timer = timer;
            if (logLevel == 0)
            {
                Console.WriteLine("Started Timer");
            }
        start:
            //Wait for user input
            string input = Console.ReadLine();
            //Reconnect the Lighting controller
            if (input.ToLower() == "reconnect")
            {
                try
                {
                    lighting.Disconnect();
                    if (logLevel < 2)
                    {
                        Console.WriteLine("Reconnecting Lighting controller");
                    }
                    lighting.Connect();
                    if (logLevel == 0)
                    {
                        Console.WriteLine("Lighting controller reconnected successfully");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                goto start;
            }
            //Add a new ZWave device to the controller
            else if (input.ToLower() == "add")
            {
                try
                {
                    lighting.AddDevice();
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                goto start;
            }
            //Change the Carousel plugin
            else if (input.ToLower().Contains("carousel"))
            {
                plugins = PluginManager.LoadPlugins(AppDomain.CurrentDomain.BaseDirectory, "Carousel");

                //Find the Carousel 
                if (plugins.Count > 0)
                {
                    Console.WriteLine("Please select the appropriate Carousel plugin:");

                    for (int i = 0; i < plugins.Count; i++)
                    {
                        Console.WriteLine((i + 1).ToString() + ": " + plugins[i].TypeName);

                    }
                    string newCarousel = Console.ReadLine();
                    if (newCarousel.Contains(":"))
                    {
                        newCarousel = newCarousel.Substring(0, newCarousel.IndexOf(":"));
                    }
                    carousel = (Carousel)PluginManager.CreateInstance(plugins[int.Parse(newCarousel) - 1]);
                    key = Registry.LocalMachine.OpenSubKey("Software\\House of the Future", true);
                    key.SetValue("carousel", plugins[int.Parse(newCarousel) - 1].TypeName);
                    key = null;
                    FindCarousel();
                    newCarousel = null;
                    
                }
                else
                {
                    Console.WriteLine("No compatible plugins found.");
                }
                plugins = null;
                goto start;
            }
            //Change the Lighting plugin
            else if (input.ToLower().Contains("lighting"))
            {
                plugins = PluginManager.LoadPlugins(AppDomain.CurrentDomain.BaseDirectory, "Lighting");

                //Find the Lighting plugin 
                if (plugins.Count > 0)
                {
                    Console.WriteLine("Please select the appropriate Lighting plugin:");

                    for (int i = 0; i < plugins.Count; i++)
                    {
                        Console.WriteLine((i + 1).ToString() + ": " + plugins[i].TypeName);

                    }
                    string newLighting = Console.ReadLine();
                    if (newLighting.Contains(":"))
                    {
                        newLighting = newLighting.Substring(0, newLighting.IndexOf(":"));
                    }
                    lighting = (Lighting)PluginManager.CreateInstance(plugins[int.Parse(newLighting) - 1]);
                    key = Registry.LocalMachine.OpenSubKey("Software\\House of the Future", true);
                    key.SetValue("lighting", plugins[int.Parse(newLighting) - 1].TypeName);
                    key = null;
                    newLighting = null;

                }
                else
                {
                    Console.WriteLine("No compatible plugins found.");
                }
                plugins = null;
                goto start;
            }
            //Remove a Lighting device from the controller
            else if (input.ToLower() == "remove")
            {
                try
                {
                    lighting.RemoveDevice();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
                goto start;
            }
            //Restart the input loop if we didn't receive an Exit command
            else if (input.ToLower() != "exit")
            {
                Console.WriteLine("Unknown Command: " + input);
                goto start;
            }
            if (lighting != null)
            {
                lighting.Disconnect();
            }
            input = null;
        }

        /// <summary>
        /// Timer that checks the database for changes
        /// </summary>
        /// <param name="State"></param>
        static void timer_tick(Object State)
        {
            //Get the Last Modified Date of the database file
            System.IO.FileInfo info = new System.IO.FileInfo(database);
            //Lock the routine so only one thread can access it
            lock (IsUpdating)
            {
                //If the Last Modified Date is greater than the last time we checked, continue
                if (info.LastWriteTime > dbUpdated)
                {
                    if (logLevel < 2)
                    {
                        Console.WriteLine("Pulling light levels from the database");
                    }
                    //Set our Last Modified variable to the Last Modified Time of the database 
                    dbUpdated = info.LastWriteTime;
                    if (logLevel == 0)
                    {
                        Console.WriteLine("Database Last Write Time = " + dbUpdated.ToString());
                    }
                    info = null;
                }
            }
        }
        
        /// <summary>
        /// Find the HID handle for the Carousel tower
        /// </summary>
        static void FindCarousel()
        {
            //Create a new USB Controller
            using (USBSharp myUsb = new USBSharp())
            {
                if (logLevel < 2)
                {
                    Console.WriteLine("Finding Carousel");
                }
                myUsb.CT_HidGuid();
                //Get all HID devices connected
                myUsb.CT_SetupDiGetClassDevs();

                int result = -1;
                int device_count = 0;
                int size = 0;
                int requiredSize = 0;
                //Enumerate through the HID Devices to find the Carousel
                while (result != 0)
                {
                    result = myUsb.CT_SetupDiEnumDeviceInterfaces(device_count);
                    int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, 0);
                    size = requiredSize;
                    resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetailx(ref requiredSize, size);
                    //Vendor PID from plugin
                    if (myUsb.DevicePathName.IndexOf(carousel.VendorID) > 0)
                    {
                        //Set the path = Path for the found Stakka
                        if (logLevel == 0)
                        {
                            Console.WriteLine("Found Carousel at " + myUsb.DevicePathName);
                        }
                        path = myUsb.DevicePathName;
                        break;
                    }
                    device_count++;
                }

                if (path == null)
                {
                    Console.WriteLine("No Carousel found");
                }
                else 
                {
                    //Create a File Path for the HID device
                    myUsb.CT_CreateFile(path);
                    int myPtrToPreparsedData = -1;
                    //If we know about the device and it is ready to go...
                    if (myUsb.CT_HidD_GetPreparsedData(myUsb.HidHandle, ref myPtrToPreparsedData) != 0)
                    {
                        //Create and start a new Disk Stakka listner for the HID device
                        if (logLevel < 2)
                        {
                            Console.WriteLine("Starting Carousel listener");
                        }
                        carousel.path = path;
                        //Start a background thread to run the Carousel listener.  
                        //We do this as a USB device can't be accessed from a different thread than the one that created it
                        System.Threading.ThreadStart job = new System.Threading.ThreadStart(carousel.Initialize);
                        System.Threading.Thread t = new System.Threading.Thread(job);
                        t.Start();
                    }
                }
            }
        }

    }
}
